/**
 * Author:   e20160504
 * Date:     2018/7/9 13:19
 * Description:
 */
package com.kevin.demo.utils;

import com.google.common.collect.Maps;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ReflectionUtil {

    private static final String SETTER_PREFIX = "set";

    private static final String GETTER_PREFIX = "get";

    private static final String CGLIB_CLASS_SEPARATOR = "$$";

    private static String[] types = {"java.lang.Integer",
            "java.lang.Double",
            "java.lang.Float",
            "java.lang.Long",
            "java.lang.Short",
            "java.lang.Byte",
            "java.lang.Boolean",
            "java.lang.Character",
            "java.lang.String",
            "int","double","long","short","byte","boolean","char","float"};

    private static Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);

    /**
     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
     */
    public static Object getFieldValue(final Object obj, final String fieldName) {
        Field field = getAccessibleField(obj, fieldName);

        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
        }

        Object result = null;
        try {
            result = field.get(obj);
        } catch (IllegalAccessException e) {
            logger.error("不可能抛出的异常{}", e.getMessage());
        }
        return result;
    }

    /**
     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
     */
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
        Field field = getAccessibleField(obj, fieldName);

        if (field == null) {
            logger.error("Could not find field [" + fieldName + "] on target [" + obj + "]");
            return;
            //throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
        }
        try {
            field.set(obj, convert(value, field.getType()));
        } catch (IllegalAccessException e) {
            logger.error("不可能抛出的异常:{}", e.getMessage());
        }
    }

    public static Object convert(Object object, Class<?> type) {
        if (object instanceof Number) {
            Number number = (Number) object;
            if (type.equals(byte.class) || type.equals(Byte.class)) {
                return number.byteValue();
            }
            if (type.equals(short.class) || type.equals(Short.class)) {
                return number.shortValue();
            }
            if (type.equals(int.class) || type.equals(Integer.class)) {
                return number.intValue();
            }
            if (type.equals(long.class) || type.equals(Long.class)) {
                return number.longValue();
            }
            if (type.equals(float.class) || type.equals(Float.class)) {
                return number.floatValue();
            }
            if (type.equals(double.class) || type.equals(Double.class)) {
                return number.doubleValue();
            }
        }
        if (type.equals(String.class)) {
            return object == null ? "" : object.toString();
        }
        return object;
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符.
     * 用于一次性调用的情况，否则应使用getAccessibleMethod()函数获得Method后反复调用.
     * 同时匹配方法名+参数类型，
     */
    public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
                                      final Object[] args) {
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null) {
            throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
        }

        try {
            return method.invoke(obj, args);
        } catch (Exception e) {
            throw convertReflectionExceptionToUnchecked(e);
        }
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符，
     * 用于一次性调用的情况，否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
     * 只匹配函数名，如果有多个同名函数调用第一个。
     */
    public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
        Method method = getAccessibleMethodByName(obj, methodName);
        if (method == null) {
            throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
        }

        try {
            return method.invoke(obj, args);
        } catch (Exception e) {
            throw convertReflectionExceptionToUnchecked(e);
        }
    }

    /**
     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
     * <p>
     * 如向上转型到Object仍无法找到, 返回null.
     */
    public static Field getAccessibleField(final Object obj, final String fieldName) {
        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
            try {
                Field field = superClass.getDeclaredField(fieldName);
                makeAccessible(field);
                return field;
            } catch (NoSuchFieldException e) {//NOSONAR
                // Field不在当前类定义,继续向上转型
                continue;// new add
            }
        }
        return null;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 匹配函数名+参数类型。
     * <p>
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    public static Method getAccessibleMethod(final Object obj, final String methodName,
                                             final Class<?>... parameterTypes) {

        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            try {
                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
                makeAccessible(method);
                return method;
            } catch (NoSuchMethodException e) {
                // Method不在当前类定义,继续向上转型
                continue;// new add
            }
        }
        return null;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 只匹配函数名。
     * <p>
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    public static Method getAccessibleMethodByName(final Object obj, final String methodName) {

        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            Method[] methods = searchType.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    makeAccessible(method);
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 改变private/protected的方法为public，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Method method) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
                && !method.isAccessible()) {
            method.setAccessible(true);
        }
    }

    /**
     * 改变private/protected的成员变量为public，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Field field) {
        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
                .isFinal(field.getModifiers())) && !field.isAccessible()) {
            field.setAccessible(true);
        }
    }

    /**
     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
     * 如无法找到, 返回Object.class.
     * eg.
     * public UserDao extends HibernateDao<User>
     *
     * @param clazz The class to introspect
     * @return the first generic declaration, or Object.class if cannot be determined
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getClassGenricType(final Class clazz) {
        return getClassGenricType(clazz, 0);
    }

    /**
     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
     * 如无法找到, 返回Object.class.
     * <p>
     * 如public UserDao extends HibernateDao<User,Long>
     *
     * @param clazz clazz The class to introspect
     * @param index the Index of the generic ddeclaration,start from 0.
     * @return the index generic declaration, or Object.class if cannot be determined
     */
    public static Class getClassGenricType(final Class clazz, final int index) {

        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType)) {
            logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
            return Object.class;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if (index >= params.length || index < 0) {
            logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
                    + params.length);
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
            logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
            return Object.class;
        }

        return (Class) params[index];
    }

    public static Class<?> getUserClass(Object instance) {
        Assert.notNull(instance, "Instance must not be null");
        Class clazz = instance.getClass();
        if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null && !Object.class.equals(superClass)) {
                return superClass;
            }
        }
        return clazz;

    }

    /**
     * 将反射时的checked exception转换为unchecked exception.
     */
    public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
                || e instanceof NoSuchMethodException) {
            return new IllegalArgumentException(e);
        } else if (e instanceof InvocationTargetException) {
            return new RuntimeException(((InvocationTargetException) e).getTargetException());
        } else if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        }
        return new RuntimeException("Unexpected Checked Exception.", e);
    }

    /**
     * 判断某个对象是否拥有某个属性
     *
     * @param obj       对象
     * @param fieldName 属性名
     * @return 有属性返回true
     * 无属性返回false
     */
    public static boolean hasField(final Object obj, final String fieldName) {
        Field field = getAccessibleField(obj, fieldName);
        if (field == null) {
            return false;
        }
        return true;

    }

    /**
     * 获取两个对象同名属性内容不相同的列表
     *
     * @param class1 old对象 必须是普通javabean 不能是包装类
     * @param class2 new对象 必须是普通javabean 不能是包装类
     * @param annotation Class对象 swagger的ApiModelProperty
     * @return
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     */
    public static <T> List<Map<String, Object>> compareTwoClass(Object class1, Object class2, List<String> uncluedColums, List<String> updateColums,Class<? extends ApiModelProperty> annotation) throws Exception {

        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        //获取对象的class
        Class<?> clazz1 = class1.getClass();
        Class<?> clazz2 = class2.getClass();
        //获取对象的属性列表
        Field[] field1 = clazz1.getDeclaredFields();
        Field[] field2 = clazz2.getDeclaredFields();
        //遍历属性列表field1
        for (int i = 0; i < field1.length; i++) {
            String name1 = field1[i].getName();
            String name2 = clazz2.getDeclaredField(name1).getName();
            if ((!CollectionUtils.isEmpty(uncluedColums) && (uncluedColums.contains(name1) || uncluedColums.contains(name2)))) {
                continue;
            }
            if (!CollectionUtils.isEmpty(updateColums) && (updateColums.contains(name1) || updateColums.contains(name2))) {
                if (name1.equals(name2)) {
                    field1[i].setAccessible(true);
                    field2[i].setAccessible(true);
                    if (!compareTwo(field1[i].get(class1), field2[i].get(class2))) {
                        Map<String, Object> map2 = new HashMap<String, Object>();
                        ApiModelProperty name = field1[i].getAnnotation(annotation);
                        String fieldName = "";
                        if (name != null) {
                            fieldName = name.value();
                        } else {
                            fieldName = field1[i].getName();
                        }
                        map2.put("name", fieldName);
                        map2.put("old", field1[i].get(class1));
                        map2.put("new", field2[i].get(class2));
                        list.add(map2);
                    }
                }
            }
        }
        return list;
    }

    /**
    *@params: entity
    *@return: Map<String,Ojbect>
    *@desc: 将实体类不为null的字段转换成map
    */
    public static Map<String,Object> obj2Map(Object obj){
        Map<String,Object> map = Maps.newHashMap();
        if(obj == null){
            return map;
        }
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                if(field.get(obj) != null) {
                    map.put(field.getName(), field.get(obj));
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 获取两个对象同名属性内容不相同的列表
     *
     * @param class1 old对象 必须是普通javabean 不能是包装类
     * @param class2 new对象 必须是普通javabean 不能是包装类
     * @return
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @desc: 使用自定义的注解
     */
    //public static List<Map<String, Object>> compareTwoClass(Object class1, Object class2) throws Exception {
    //
    //    List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
    //    //获取对象的class
    //    Class<?> clazz1 = class1.getClass();
    //    Class<?> clazz2 = class2.getClass();
    //    //获取对象的属性列表
    //    Field[] field1 = clazz1.getDeclaredFields();
    //    Field[] field2 = clazz2.getDeclaredFields();
    //    //遍历属性列表field1
    //    for (int i = 0; i < field1.length; i++) {
    //        String name1 = field1[i].getName();
    //        String name2 = clazz2.getDeclaredField(name1).getName();
    //        if (name1.equals(name2)) {
    //            field1[i].setAccessible(true);
    //            field2[i].setAccessible(true);
    //            if (!compareTwo(field1[i].get(class1), field2[i].get(class2))) {
    //                Map<String, Object> map2 = new HashMap<String, Object>();
    //                FeildDeclare name = field1[i].getAnnotation(FeildDeclare.class);
    //                String fieldName = "";
    //                if (name != null) {
    //                    fieldName = name.name();
    //                } else {
    //                    fieldName = field1[i].getName();
    //                }
    //                map2.put("name", fieldName);
    //                map2.put("old", field1[i].get(class1));
    //                map2.put("new", field2[i].get(class2));
    //                list.add(map2);
    //            }
    //        }
    //    }
    //    return list;
    //}

    /**
     * 对比两个数据是否内容相同
     *
     * @param object1,object2
     * @return boolean类型
     */
    public static boolean compareTwo(Object object1, Object object2) {

        if (object1 == null && object2 == null) {
            return true;
        }
        if (object1 == null && object2 != null) {
            return false;
        }
        if(object1 !=null && object2 == null){
            return false;
        }
        if ((object1.toString()).equals((object2.toString()))) {
            return true;
        }
        return false;
    }

    /**
    *@params:
    *@return:
    *@desc: 判断类的属性类型是否是基本类型
    */
    public static boolean isBaseType(Field field){
        String typeName = field.getGenericType().getTypeName();
        for (int i = 0; i < types.length; i++) {
            if(types[i].equalsIgnoreCase(typeName)){
                return true;
            }
        }
        return false;
    }
}
